﻿#include "widget.h"
#include <QMessageBox>
#include <thread>
#include <QDebug>
#include <QFileDialog>
#include <QList>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QCloseEvent>
#include <QPixmap>
#include <QBitmap>
#include <QRegExp>
#include <QPainter>
#include <QScrollBar>
#include <QProcessEnvironment>

#pragma comment(lib,"D:\\code\\Qt\\encryptImg\\libs\\libdecryption.lib")
#pragma comment(lib,"D:\\code\\Qt\\encryptImg\\libs\\libencryption.lib")
#pragma comment(lib,"C:\\Program Files\\MATLAB\\R2015b\\extern\\lib\\win64\\microsoft\\mclmcrrt.lib")


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    initOk(false),
    isRunning(false),
    pool(8)
{
    //build ui
    log=new QListWidget;
    fileListWgt=new QTableWidget;
    addFile=new QPushButton(ADDFILE);
    deleteFile=new QPushButton(DELETEFILE);
    encrypt=new QPushButton(ENCRYPT);
    decrypt=new QPushButton(DECRYPT);
    clearList=new QPushButton(CLEARLIST);
    clearLog=new QPushButton(CLEARLOG);
    ad=new QTextBrowser;
    progress=new QProgressBar;
    psLab=new QLabel(PASSWORD);
    psLab2=new QLabel(PASSWORD2);
    prefixLab=new QLabel(PREFIX);
    maxThreadLab=new QLabel(MAXTHREAD);
    passwordWgt=new QLineEdit;
    passwordWgt2=new QLineEdit;
    prefixWgt=new QLineEdit;
    maxThreadWgt=new QLineEdit;

    //set ui layout
    QVBoxLayout *mainLayout=new QVBoxLayout;
    QHBoxLayout *mainWnd=new QHBoxLayout;
    QVBoxLayout *rightLayout=new QVBoxLayout;
    QGridLayout *psprefixLayout=new QGridLayout;
    setLayout(mainLayout);
    mainLayout->addLayout(mainWnd);
    mainLayout->addWidget(progress);
    mainWnd->addWidget(log);
    mainWnd->addWidget(fileListWgt);
    mainWnd->addLayout(rightLayout);
    rightLayout->addWidget(addFile);
    rightLayout->addWidget(deleteFile);
    rightLayout->addWidget(clearList);
    rightLayout->addWidget(ad);
    rightLayout->addLayout(psprefixLayout);
    rightLayout->addWidget(encrypt);
    rightLayout->addWidget(decrypt);
    rightLayout->addWidget(clearLog);
    psprefixLayout->addWidget(psLab,0,0);
    psprefixLayout->addWidget(passwordWgt,0,1);
    psprefixLayout->addWidget(psLab2,1,0);
    psprefixLayout->addWidget(passwordWgt2,1,1);
    psprefixLayout->addWidget(prefixLab,2,0);
    psprefixLayout->addWidget(prefixWgt,2,1);
    psprefixLayout->addWidget(maxThreadLab,3,0);
    psprefixLayout->addWidget(maxThreadWgt,3,1);


    //connect
    connect(this,SIGNAL(freshStatus(QString,QString,bool)),this,SLOT(on_freshStatus(QString,QString,bool)));
    connect(this,SIGNAL(setRunningStatus(bool)),this,SLOT(on_setRunningStatus(bool)));
    connect(this,SIGNAL(flushUI()),this,SLOT(on_flushUI()));
    connect(this,SIGNAL(errorMsg(QString)),this,SLOT(on_errorMsg(QString)));
    connect(this,SIGNAL(showLog(QString)),this,SLOT(on_showLog(QString)));
    connect(deleteFile,SIGNAL(clicked(bool)),this,SLOT(on_deleteFile_clicked()));
    connect(addFile,SIGNAL(clicked(bool)),this,SLOT(on_addFile_clicked()));
    connect(clearList,SIGNAL(clicked(bool)),this,SLOT(on_clearList_clicked()));
    connect(encrypt,SIGNAL(clicked(bool)),this,SLOT(on_encrypt_clicked()));
    connect(decrypt,SIGNAL(clicked(bool)),this,SLOT(on_decrypt_clicked()));
    connect(clearLog,SIGNAL(clicked(bool)),this,SLOT(on_clearLog_clicked()));
    connect(this,SIGNAL(setAllStatus(QString)),this,SLOT(on_setAllStatus(QString)));


    //other setting

    setStyleSheet(STYLESHEET);
    log->setStyleSheet(STYLESHEET1);
    fileListWgt->setStyleSheet(STYLESHEET1);
    passwordWgt->setStyleSheet(STYLESHEET2);
    passwordWgt2->setStyleSheet(STYLESHEET2);
    prefixWgt->setStyleSheet(STYLESHEET2);
    maxThreadWgt->setStyleSheet(STYLESHEET2);

    setWindowIcon(QIcon(LOGOPATH));
    log->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    log->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    fileListWgt->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    progress->setValue(0);
    psLab->setAlignment(Qt::AlignRight);
    psLab2->setAlignment(Qt::AlignRight);
    prefixLab->setAlignment(Qt::AlignRight);
    maxThreadLab->setAlignment(Qt::AlignRight);
    passwordWgt->setEchoMode(QLineEdit::Password);
    passwordWgt2->setEchoMode(QLineEdit::Password);

    fileListWgt->setSelectionBehavior ( QAbstractItemView::SelectRows);
    fileListWgt->setSelectionMode ( QAbstractItemView::SingleSelection);
    fileListWgt->setSelectionMode(QAbstractItemView::ExtendedSelection);

    setWindowTitle(PROGRAMTITLE+VERSION);
    QString number_of_processors=QProcessEnvironment::systemEnvironment().value("NUMBER_OF_PROCESSORS","8");
    maxThreadWgt->setText(number_of_processors);
    setGeometry(100,100,1200,650);
    on_flushUI();
    ad->setHtml(ADCONTENT);

    std::thread([=](){
        std::thread([&](){
            if(mclInitializeApplication(0,0)){
                emit showLog(MCL+INITOK);
            }else{
                emit errorMsg(MCL+INITERROR);
                exit(0);
            }
        }).join();
        std::thread th1([&](){
            if(libencryptionInitialize()){
                emit on_showLog(LIBENCRYPTION+INITOK);
            }else{
                emit errorMsg(LIBENCRYPTION+INITERROR);
                exit(0);
            }
        });
        std::thread th2([&](){
            if(libdecryptionInitialize()){
                emit on_showLog(LIBDECRYPTION+INITOK);
            }else{
                emit errorMsg(LIBDECRYPTION+INITERROR);
                exit(0);
            }
        });
        th1.join();
        th2.join();
        initOk=true;
        on_showLog(INITFINISH);
    }).detach();
}

Widget::~Widget()
{

}

void Widget::on_addFile_clicked()
{
    if(isRunning){
        on_errorMsg(OTHERPRO);
        return ;
    }
    QStringList filelst= QFileDialog::getOpenFileNames(this,OPENFILE,"",FILEFILTER);
    QSet<QString> tmpSet= filelst.toSet();
    for(auto &p:fileList){
        p=UNKNOWN;
    }
    for(auto p:tmpSet){
        fileList.insert(p,UNKNOWN);
    }
    on_flushUI();
}


void Widget::on_showLog(const QString &msg){
    log->addItem(msg);
    log->verticalScrollBar()->setValue(log->verticalScrollBar()->maximum());
}

void Widget::on_errorMsg(const QString &msg){
    on_showLog(msg);
    QMessageBox::warning(this,ERRORSTR,msg);
}


void Widget::resizeEvent(QResizeEvent *){
    int w=fileListWgt->width();
    fileListWgt->setColumnWidth(0,0.7*w);
    fileListWgt->setColumnWidth(1,0.25*w);


}

void Widget::on_flushUI(){
    fileListWgt->clear();
    fileListWgt->setColumnCount(2);
    fileListWgt->setRowCount(fileList.size());
    QTableWidgetItem *tmp=new QTableWidgetItem(FILEPATH);
    QTableWidgetItem *tmp2=new QTableWidgetItem(STATUS);
    tmp->setForeground(QBrush(QColor(0,0,0)));
    fileListWgt->setHorizontalHeaderItem(0,tmp );
    tmp2->setForeground(QBrush(QColor(0,0,0)));
    fileListWgt->setHorizontalHeaderItem(1,tmp2);
    int i=0;
    for(QMap<QString,QString>::iterator p=fileList.begin();p!=fileList.end();++p){
        tmp=new QTableWidgetItem(p.key());
        tmp2=new QTableWidgetItem(p.value());
        tmp->setFlags(tmp->flags()&~Qt::ItemIsEditable);
        tmp2->setFlags(tmp2->flags()&~Qt::ItemIsEditable);
        tmp->setForeground(QBrush(QColor(255,255,255)));
        tmp2->setForeground(QBrush(QColor(255,255,255)));
        if(p.value()==UNKNOWN||p.value()==UNENCRYPTED||p.value()==UNDECRYPTED){
            tmp->setBackgroundColor(QColor(64,64,64));
            tmp2->setBackgroundColor(QColor(64,64,64));
        }else if(p.value()==ENCRYPTING||p.value()==DECRYPTING){
            tmp->setBackgroundColor(QColor(0,0,64));
            tmp2->setBackgroundColor(QColor(0,0,64));
        }else if(p.value()==ENCRYPTED||p.value()==DECRYPTED){
            tmp->setBackgroundColor(QColor(0,64,0));
            tmp2->setBackgroundColor(QColor(0,64,0));
        }else if(p.value()==ENCRYPTERROR||p.value()==DECRYPTERROR){
            tmp->setBackgroundColor(QColor(64,0,0));
            tmp2->setBackgroundColor(QColor(64,0,0));
        }
        fileListWgt->setItem(i,0,tmp);
        fileListWgt->setItem(i,1,tmp2);
        ++i;
    }
    for(int i=0;i<fileListWgt->rowCount();++i){
        tmp=new QTableWidgetItem(QString::number(i+1));
        tmp->setForeground(QBrush(QColor(0,0,0)));
        fileListWgt->setVerticalHeaderItem(i,tmp);
    }
}





void Widget::on_deleteFile_clicked()
{
    if(isRunning){
        on_errorMsg(OTHERPRO);
        return ;
    }
    QList<QTableWidgetItem*> sel= fileListWgt->selectedItems();
    if(sel.size()<=0)
        return;
    int i=0;
    for(auto p:sel){
        if((++i)%2){
            fileList.remove(p->text());
        }
    }
    on_flushUI();
}

void Widget::on_clearList_clicked()
{
    if(isRunning){
        on_errorMsg(OTHERPRO);
        return ;
    }
    fileList.clear();
    on_flushUI();
}

bool Widget::ifCanRun(){
    if(!initOk){
        on_errorMsg(UNINIT);
        return false;
    }
    if(isRunning){
        on_errorMsg(OTHERPRO);
        return false;
    }
    if(fileList.empty()){
        on_errorMsg(EMPTYLIST);
        return false;
    }
    password=passwordWgt->text().toDouble();
    if(!(password>0&&password<1)){
        on_errorMsg(PASSWORDTIP);
        return false;
    }
    if(passwordWgt2->text()!=passwordWgt->text()){
        on_errorMsg(DIFFPASSWORD);
        return false;
    }
    maxThreadNum=maxThreadWgt->text().toUInt();
    if(maxThreadNum<=0||maxThreadNum>32){
        on_errorMsg(THREADNUMERROR);
        return false;
    }
    QString prefix=prefixWgt->text();
    QRegExp reg(FILENAMEREG);
    if(!reg.exactMatch(prefix)&&!prefix.isEmpty()){
        on_errorMsg(PREFIXERROR);
        return false;
    }
    outputDir = QFileDialog::getExistingDirectory(this, SAVEFILE,outputDir,QFileDialog::ShowDirsOnly| QFileDialog::DontResolveSymlinks);
    if(outputDir.isEmpty()){
        return false;
    }
    return true;
}

void Widget::on_encrypt_clicked()
{
    if(!ifCanRun())
        return;
    QString prefix=prefixWgt->text();
    on_setRunningStatus(true);
    finishNum=0;
    pool.setMaxRunSize(maxThreadNum);
    oldTime=QTime::currentTime();
    passwordWgt->clear();
    passwordWgt2->clear();
    std::thread([=](QMap<QString,QString>fileList,QString prefix,double password,QString outputDir,ThreadPool &pool,Widget*wnd)->void {
        emit wnd->setAllStatus(UNENCRYPTED);
        for(auto p=fileList.begin();p!=fileList.end();++p){
            pool.addThread(encryptThread,outputDir,prefix,p.key(),password,wnd);
        }
    },fileList,prefix,password,outputDir,std::ref(pool),this).detach();
}


void Widget::on_decrypt_clicked()
{
    if(!ifCanRun())
        return;
    QString prefix=prefixWgt->text();
    on_setRunningStatus(true);
    finishNum=0;
    pool.setMaxRunSize(maxThreadNum);
    oldTime=QTime::currentTime();
    passwordWgt->clear();
    passwordWgt2->clear();
    std::thread([=](QMap<QString,QString>fileList,QString prefix,double password,QString outputDir,ThreadPool &pool,Widget*wnd)->void {
        emit wnd->setAllStatus(UNDECRYPTED);
        for(auto p=fileList.begin();p!=fileList.end();++p){
            pool.addThread(decryptThread,outputDir,prefix,p.key(),password,wnd);
        }
    },fileList,prefix,password,outputDir,std::ref(pool),this).detach();
}


void Widget::on_setAllStatus(const QString &status){
    for(auto p=fileList.begin();p!=fileList.end();++p){
        fileList[p.key()]=status;
    }
    flushUI();
}


void Widget::on_setRunningStatus(bool ifRun){
    if(ifRun){
        isRunning=true;
        addFile->setEnabled(false);
        deleteFile->setEnabled(false);
        decrypt->setEnabled(false);
        encrypt->setEnabled(false);
        passwordWgt->setEnabled(false);
        passwordWgt2->setEnabled(false);
        prefixWgt->setEnabled(false);
        clearList->setEnabled(false);
        maxThreadWgt->setEnabled(false);
    }else{
        isRunning=false;
        addFile->setEnabled(true);
        deleteFile->setEnabled(true);
        decrypt->setEnabled(true);
        encrypt->setEnabled(true);
        passwordWgt->setEnabled(true);
        passwordWgt2->setEnabled(true);
        prefixWgt->setEnabled(true);
        clearList->setEnabled(true);
        maxThreadWgt->setEnabled(true);
    }
}


mwArray str2Mw(QString str,Widget* wnd){
    try{
        mwArray mw(1,str.length(),mxCHAR_CLASS);
        mw.SetData((mxUint8*)str.toStdString().c_str(),str.length());
        return mw;
    }catch(...){
        emit wnd->errorMsg(STR2MW+EXCEPTION);
        return mwArray();
    }
}

mwArray num2Mw(double num,Widget* wnd){
    try{
        mwArray mw(1,1,mxDOUBLE_CLASS);
        mw.SetData((mxDouble*)&num,1);
        return mw;
    }catch(...){
        emit wnd->errorMsg(NUM2MW+EXCEPTION);
        return mwArray();
    }
}

void Widget::on_freshStatus(QString fileName,QString status,bool finish){
    finishNum+=finish;
    if(finish){
        on_showLog(fileName+status);
    }
    progress->setValue(finishNum*100/fileList.size());
    fileList[fileName]=status;
    for(int i=0;i<fileListWgt->rowCount();++i){
        if(fileListWgt->item(i,0)->text()==fileName){
            QTableWidgetItem*tmp=new QTableWidgetItem(fileName);
            QTableWidgetItem*tmp2=new QTableWidgetItem(status);
            tmp->setFlags(tmp->flags()&~Qt::ItemIsEditable);
            tmp->setForeground(QBrush(QColor(255,255,255)));
            if(status==UNKNOWN||status==UNENCRYPTED||status==UNDECRYPTED){
                tmp->setBackgroundColor(QColor(64,64,64));
                tmp2->setBackgroundColor(QColor(64,64,64));
            }else if(status==ENCRYPTING||status==DECRYPTING){
                tmp->setBackgroundColor(QColor(0,0,64));
                tmp2->setBackgroundColor(QColor(0,0,64));
            }else if(status==ENCRYPTED||status==DECRYPTED){
                tmp->setBackgroundColor(QColor(0,64,0));
                tmp2->setBackgroundColor(QColor(0,64,0));
            }else if(status==ENCRYPTERROR||status==DECRYPTERROR){
                tmp->setBackgroundColor(QColor(64,0,0));
                tmp2->setBackgroundColor(QColor(64,0,0));
            }
            fileListWgt->setItem(i,0,tmp);
            fileListWgt->setItem(i,1,tmp2);
            break;
        }
    }
    if(finishNum==fileList.size()){
        QMessageBox::information(this, TIP, FINISH+TIMECOST+QString::number(oldTime.secsTo(QTime::currentTime()))+SECOND,QMessageBox::Ok);
        progress->setValue(0);
        finishNum=0;
        on_setRunningStatus(false);
    }
}


void encryptThread(QString outputDir,QString prefix,QString filePath,double password,Widget*wnd){
    try{
        emit wnd->freshStatus(filePath,ENCRYPTING,false);
        QString tagFile=outputDir+"/"+prefix+QFileInfo(filePath).baseName()+".png";
        encryption(str2Mw(filePath,wnd),str2Mw(tagFile,wnd),num2Mw(password,wnd));
        emit wnd->freshStatus(filePath,ENCRYPTED,true);
    }catch(...){
        emit wnd->freshStatus(filePath,ENCRYPTERROR,true);
    }
}

void decryptThread(QString outputDir,QString prefix,QString filePath,double password,Widget*wnd){
    try{
        emit wnd->freshStatus(filePath,DECRYPTING,false);
        QString tagFile=outputDir+"/"+prefix+QFileInfo(filePath).baseName()+".png";
        decryption(str2Mw(filePath,wnd),str2Mw(tagFile,wnd),num2Mw(password,wnd));
        emit wnd->freshStatus(filePath,DECRYPTED,true);
    }catch(...){
        emit wnd->freshStatus(filePath,DECRYPTERROR,true);
    }
}



void Widget::closeEvent(QCloseEvent *ev){
    if(isRunning||!initOk){
        errorMsg(CANTEXIT);
        ev->ignore();
        return;
    }
    if(QMessageBox::warning(this,TIP,IFEXIT,QMessageBox::Ok,QMessageBox::Cancel)==QMessageBox::Ok){
        ev->accept();
    }else{
        ev->ignore();
    }
}

void Widget::on_clearLog_clicked(){
    log->clear();
}
